
package com.hlkj.pay.app.merchant.ext.impl.guogpay;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import cn.hutool.json.JSONObject;
import com.alibaba.fastjson.JSON;
import com.hlkj.framework.common.pojo.CommonResult;
import com.hlkj.framework.common.util.json.JsonUtils;
import com.hlkj.framework.extension.annotation.Extension;
import com.hlkj.framework.extension.executor.ExtensionExecutor;
import com.hlkj.framework.extension.scenario.BizScenario;
import com.hlkj.pay.app.merchant.ext.ChannelMerchantOrderExtPt;
import com.hlkj.pay.app.merchant.ext.MerchantExtConstant;
import com.hlkj.pay.app.merchant.ext.dto.order.ChannelPayOrderContext;
import com.hlkj.pay.app.merchant.ext.dto.order.ChannelPayOrderQueryContext;
import com.hlkj.pay.app.merchant.ext.dto.order.ChannelRefundPayOrderContext;
import com.hlkj.pay.app.merchant.ext.impl.guogpay.constant.GuogPayExtConstant;
import com.hlkj.pay.app.merchant.ext.impl.guogpay.dto.GuogPayConfig;
import com.hlkj.pay.app.merchant.ext.impl.guogpay.dto.GuogpayAppChannelParams;
import com.hlkj.pay.app.merchant.ext.impl.guogpay.dto.req.CallbackRefundRequest;
import com.hlkj.pay.app.merchant.ext.impl.guogpay.dto.req.CallbackTradeRequest;
import com.hlkj.pay.app.merchant.ext.impl.guogpay.utils.GuogPaySignUtil;
import com.hlkj.pay.common.CommonResultCode;
import com.hlkj.pay.common.MerchantResultCode;
import com.hlkj.pay.dto.order.PayOrderChannelResultDto;
import com.hlkj.pay.dto.order.notify.CsOrderNotifyDto;
import com.hlkj.pay.dto.order.notify.RefundOrderNotifyDto;
import com.hlkj.pay.enums.CommonEnum;
import com.hlkj.pay.enums.PayEnums;
import com.hlkj.pay.enums.PayOrderEnums;
import com.hlkj.pay.infrastructure.model.order.PayOrderDO;
import com.hlkj.pay.infrastructure.model.order.RefundOrderDO;
import com.hlkj.pay.infrastructure.model.pay.PayWayDO;
import com.hlkj.pay.infrastructure.model.pay.ProviderChannelConfigDO;
import com.hlkj.pay.service.order.IPayOrderService;
import com.hlkj.pay.util.DateUtils;
import com.hlkj.pay.util.HttpRequestUtil;
import com.hlkj.pay.util.MerchantUtils;
import com.hlkj.pay.vo.order.req.PayOrderCommonParamReq;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * @author HlpayTeam
 * @date 2024/10/15 18:16
 */
@Slf4j
@Repository
@RequiredArgsConstructor
@Extension(bizId = MerchantExtConstant.CHANNEL_GUOG_PAY)
public class GuogPayMerchantOrderExtPt implements ChannelMerchantOrderExtPt {

    private final ExtensionExecutor extensionExecutor;

    private final IPayOrderService payOrderService;

    @Value("${hlkj.pay.openapi.checkSign:true}")
    private boolean checkSign;

    @Override
    public CommonResult<String> channelOrderCode(ChannelPayOrderContext channelPayOrderContext) {
        return CommonResult.success();
    }

    @Override
    public void submitChannelPayOrder(ChannelPayOrderContext channelPayOrderContext) {
        PayWayDO payWayDO = channelPayOrderContext.getPayWayDO();
        String payType = getPayType(payWayDO);
        if (!StringUtils.hasText(payType)) {
            channelPayOrderContext.setRemark("当前支付方式不支持");
            channelPayOrderContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(GuogPayScenePayOrderExtPt.class, BizScenario.valueOf(payType), ex -> ex.submitChannelPayOrder(channelPayOrderContext));

    }

    @Override
    public void queryChannelPayOrder(ChannelPayOrderQueryContext channelPayOrderQueryContext) {
        PayWayDO payWayDO = channelPayOrderQueryContext.getPayWayDO();
        String payType = getPayType(payWayDO);
        if (!StringUtils.hasText(payType)) {
            channelPayOrderQueryContext.setRemark("当前支付方式不支持");
            channelPayOrderQueryContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(GuogPayScenePayOrderExtPt.class, BizScenario.valueOf(payType), ex -> ex.queryChannelPayOrder(channelPayOrderQueryContext));

    }

    @Override
    public void closeChannelPayOrder(ChannelPayOrderQueryContext channelPayOrderQueryContext) {
        PayWayDO payWayDO = channelPayOrderQueryContext.getPayWayDO();
        String payType = getPayType(payWayDO);
        if (!StringUtils.hasText(payType)) {
            channelPayOrderQueryContext.setRemark("当前支付方式不支持");
            channelPayOrderQueryContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(GuogPayScenePayOrderExtPt.class, BizScenario.valueOf(payType), ex -> ex.closeChannelPayOrder(channelPayOrderQueryContext));

    }

    @Override
    public void queryChannelClosePayOrder(ChannelPayOrderQueryContext channelPayOrderQueryContext) {
        PayWayDO payWayDO = channelPayOrderQueryContext.getPayWayDO();
        String payType = getPayType(payWayDO);
        if (!StringUtils.hasText(payType)) {
            channelPayOrderQueryContext.setRemark("当前支付方式不支持");
            channelPayOrderQueryContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(GuogPayScenePayOrderExtPt.class, BizScenario.valueOf(payType), ex -> ex.queryChannelClosePayOrder(channelPayOrderQueryContext));

    }

    @Override
    public boolean refundApply(ChannelRefundPayOrderContext channelRefundPayOrderContext) {
        PayWayDO payWayDO = channelRefundPayOrderContext.getPayWayDO();
        String payType = getPayType(payWayDO);
        if (!StringUtils.hasText(payType)) {
            channelRefundPayOrderContext.setRemark("当前支付方式不支持");
            channelRefundPayOrderContext.setProcessStatus(Boolean.FALSE);
            return false;
        }
        return extensionExecutor.execute(GuogPayScenePayOrderExtPt.class, BizScenario.valueOf(payType), ex -> ex.refundApply(channelRefundPayOrderContext));
    }

    @Override
    public void refundChannelPayOrder(ChannelRefundPayOrderContext channelRefundPayOrderContext) {
        PayWayDO payWayDO = channelRefundPayOrderContext.getPayWayDO();
        String payType = getPayType(payWayDO);
        if (!StringUtils.hasText(payType)) {
            channelRefundPayOrderContext.setRemark("当前支付方式不支持");
            channelRefundPayOrderContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(GuogPayScenePayOrderExtPt.class, BizScenario.valueOf(payType), ex -> ex.refundChannelPayOrder(channelRefundPayOrderContext));

    }

    @Override
    public void queryRefundChannelPayOrder(ChannelRefundPayOrderContext channelRefundPayOrderContext) {
        PayWayDO payWayDO = channelRefundPayOrderContext.getPayWayDO();
        String payType = getPayType(payWayDO);
        if (!StringUtils.hasText(payType)) {
            channelRefundPayOrderContext.setRemark("当前支付方式不支持");
            channelRefundPayOrderContext.setProcessStatus(Boolean.FALSE);
            return;
        }
        extensionExecutor.executeVoid(GuogPayScenePayOrderExtPt.class, BizScenario.valueOf(payType), ex -> ex.queryRefundChannelPayOrder(channelRefundPayOrderContext));
    }

    @Override
    public CommonResult<PayOrderChannelResultDto> orderStatusParse(HttpServletRequest servletRequest, ProviderChannelConfigDO providerChannelConfigDO) {
        String requestContext = null;
        try {
            // 获取完整的 JSON 字符串
            requestContext = params(servletRequest);
        }
        catch (Exception e) {
            log.error("从当前线程中获取参数失败 从流中重新获取失败:{}", e.getMessage(),e);
            return CommonResult.error(CommonResultCode.PARAMETER_MISSING);
        }
        JSONObject obj = JsonUtils.parseObj(requestContext);
        CommonResult<PayOrderChannelResultDto> payOrderDOCommonResult;
        if (obj.containsKey("outRefundNo")) {
            return CommonResult.success();
        } else {
            CallbackTradeRequest request = JSON.parseObject(requestContext, CallbackTradeRequest.class);
            GuogPayConfig payConfig = getPayConfig(providerChannelConfigDO.getParamsConfig(), request.getOutTradeNo());
            payOrderDOCommonResult = processCommonCall(request);
            try {
                if (request.getSign().equals(GuogPaySignUtil.sign(request, payConfig.getMd5Key()))) {
                    log.info("国广支付支付回调参数:{}", requestContext);
                    if (payOrderDOCommonResult.getData() != null)
                        payOrderDOCommonResult.getData().setCallBackBody(requestContext);
                } else {
                    log.error("国广支付签名错误:{}", "验签失败");
                    payOrderDOCommonResult.setCode(MerchantResultCode.MERCHANT_CHANNEL_CALLBACK_SIGN_ERROR.getCode());
                    return payOrderDOCommonResult;
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        return payOrderDOCommonResult;
    }

    @Override
    public CommonResult<PayOrderChannelResultDto> orderStatusParse(HttpServletRequest request) {
        return null;
    }

    @Override
    public CommonResult<PayOrderChannelResultDto> merAuthParse(HttpServletRequest request) {
        return null;
    }

    @Override
    public PayOrderDO closeOrderStatusParse(HttpServletRequest callBackBody, ProviderChannelConfigDO providerChannelConfigDO) {
        return null;
    }

    @Override
    public CommonResult<CsOrderNotifyDto> csOrderStatusParse(HttpServletRequest request, ProviderChannelConfigDO providerChannelConfigDO) {
        return null;
    }

    @Override
    public CommonResult<RefundOrderNotifyDto> refundStatusParse(HttpServletRequest callBackBody, ProviderChannelConfigDO providerChannelConfigDO) {
        String requestContext = HttpRequestUtil.requestBodyParams(callBackBody);
        log.info("国通退款订单参数:{}", requestContext);
        CallbackRefundRequest refundRequest = JsonUtils.parseObject(requestContext, CallbackRefundRequest.class);
        CommonResult<RefundOrderNotifyDto> payOrderDOCommonResult = CommonResult.success();
        try {
            GuogPayConfig payConfig = getPayConfig(providerChannelConfigDO.getParamsConfig(), refundRequest.getOutTradeNo());
            refundRequest.setVersion("");
            String reqSign = GuogPaySignUtil.sign(refundRequest, payConfig.getMd5Key());
            if (!refundRequest.getSign().equals(reqSign)) {
                log.info("通退款订单回调验签不通过,signature:{}", requestContext);
                payOrderDOCommonResult.setCode(MerchantResultCode.MERCHANT_CHANNEL_CALLBACK_SIGN_ERROR.getCode());
            }
        } catch (Exception e) {
            e.printStackTrace();
            payOrderDOCommonResult.setCode(MerchantResultCode.MERCHANT_CHANNEL_CALLBACK_SIGN_ERROR.getCode());
        }

        RefundOrderNotifyDto refundOrderNotifyDto = new RefundOrderNotifyDto();
        refundOrderNotifyDto.setCallBackBody(requestContext);
        RefundOrderDO updateRefundOrderDO = new RefundOrderDO();
        refundOrderNotifyDto.setRefundOrder(updateRefundOrderDO);
        updateRefundOrderDO.setRefundOrderNo(refundRequest.getOutRefundNo());
        switch (refundRequest.getRefundStatus()) {
            case "REFUND_SUCCESS":
                updateRefundOrderDO.setRefundStatus(PayOrderEnums.REFUND_STATUS.REFUND.getCode());
                updateRefundOrderDO.setSuccessTime(DateUtils.getCurrentTime());
                break;
            default:
                updateRefundOrderDO.setRefundStatus(PayOrderEnums.REFUND_STATUS.FAIL.getCode());
        }
        updateRefundOrderDO.setActualRefundAmount(MerchantUtils.fenToYuan(refundRequest.getRefundAmount()));
        payOrderDOCommonResult.setData(refundOrderNotifyDto);
        log.info("国广收到退款订单回调,解析后的订单数据为 payOrderDO:{}", JsonUtils.toJsonString(updateRefundOrderDO));
        return payOrderDOCommonResult;
    }

    @Override
    public String channelPayCode(PayWayDO payWayDO) {
        return getPayType(payWayDO);
    }

    @Override
    public Long queryChannelPayAppid(ChannelPayOrderContext channelPayOrderContext) {
        PayWayDO payWayDO = channelPayOrderContext.getPayWayDO();
        PayEnums.SCENE_PAY_TYPE scenePayType = PayEnums.SCENE_PAY_TYPE.fromByType(payWayDO.getSubType());
        if (scenePayType == null) {
            return null;
        }
        CommonEnum.COMMON_APP_TYPE commonAppType = null;
        switch (PayEnums.PAY_TYPE.fromByCode(payWayDO.getType())) {
            case WECHAT:
                switch (scenePayType) {
                    case QR:
                    case JSAPI:
                        commonAppType = CommonEnum.COMMON_APP_TYPE.WECHAT_PUB;
                        break;
                    // case MINI_APP:
                    case H5:
                    case APP:
                        commonAppType = CommonEnum.COMMON_APP_TYPE.WECHAT_MIN;
                        break;
                }
                break;
            case ALIPAY:
                switch (scenePayType) {
                    case JSAPI:
                        commonAppType = CommonEnum.COMMON_APP_TYPE.ALIPAY_PUB;
                        break;
                    case MINI_APP:
                        commonAppType = CommonEnum.COMMON_APP_TYPE.ALIPAY_MIN;
                        break;
                }
                break;
        }
        if (commonAppType == null) {
            return null;
        }

        ProviderChannelConfigDO providerChannelConfigDO = channelPayOrderContext.getProviderChannelConfigDO();
        switch (commonAppType) {
            case WECHAT_PUB:
                return providerChannelConfigDO.getWechatDefaultPubId();
            case WECHAT_MIN:
                return providerChannelConfigDO.getWechatDefaultMiniId();
            case ALIPAY_PUB:
                return providerChannelConfigDO.getAlipayPubId();
            case ALIPAY_MIN:
                return providerChannelConfigDO.getAlipayMiniId();
        }
        return null;
    }

    @Override
    public String callBackResponse(CommonResult commonResult) {
        String rspStr = "SUCCESS";
        if (!commonResult.isSuccess()) {
            rspStr = "FAIL";
        }
        return rspStr;
    }

    @Override
    public boolean wechatLink(ChannelPayOrderContext channelPayOrderContext) {
        PayOrderCommonParamReq.Extra extra = channelPayOrderContext.getExtra();
        if (extra != null && !CommonEnum.YES_NO_TYPE.NO.getCode().equals(extra.getOriginalType())) {
            return false;
        }
        PayWayDO payWayDO = channelPayOrderContext.getPayWayDO();
        PayEnums.SCENE_PAY_TYPE scenePayType = PayEnums.SCENE_PAY_TYPE.fromByType(payWayDO.getSubType());
        PayEnums.PAY_TYPE payType = PayEnums.PAY_TYPE.fromByCode(payWayDO.getType());
        // 只有微信 app h5才需要
        switch (payType) {
            case WECHAT:
                break;
            default:
                return false;
        }
        switch (scenePayType) {
            case APP:
            case H5:
                break;
            default:
                return false;
        }
        return true;
    }

    /**
     * 汇付支付方式路由
     *
     * @param payWayDO
     * @return
     */
    private String getPayType(PayWayDO payWayDO) {
        log.info("国广支付实际支付type:{}", payWayDO.getSubType());
        PayEnums.SCENE_PAY_TYPE scenePayType = PayEnums.SCENE_PAY_TYPE.fromByType(payWayDO.getSubType());
        if (scenePayType == null) {
            return null;
        }
        switch (scenePayType) {
            case QR:
            case JSAPI:
            case H5:
            case MINI_APP:
                return GuogPayExtConstant.PAY;
            default:
                return null;
        }
    }

    /**
     * 通用回调参数解析
     *
     * @param tradeRequest
     * @return
     */
    CommonResult<PayOrderChannelResultDto> processCommonCall(CallbackTradeRequest tradeRequest) {
        log.info("国广支付支付回调对象参数:{}", tradeRequest);

        PayOrderChannelResultDto payOrderChannelResultDto = new PayOrderChannelResultDto();
        PayOrderDO updartePayOrderDO = new PayOrderDO();
        payOrderChannelResultDto.setUpdatePayOrderDO(updartePayOrderDO);
        CommonResult<PayOrderChannelResultDto> commonResult = CommonResult.success(payOrderChannelResultDto);
        updartePayOrderDO.setPayOrderNo(tradeRequest.getOutTradeNo());
        if (tradeRequest.getStatus().equals("SUCCESS")) {
            updartePayOrderDO.setState(PayOrderEnums.PAY_ORDER_STATUS.SUCCESS.getCode());
            updartePayOrderDO.setSuccessTime(GuogPaySignUtil.converDate(tradeRequest.getPayTime()));
        }
        if (StringUtils.hasText(tradeRequest.getChannelTradeNo())) {
            updartePayOrderDO.setChannelOrderNo(tradeRequest.getChannelTradeNo());
        }
        updartePayOrderDO.setPayAmount(MerchantUtils.fenToYuan(tradeRequest.getAmount()));
        updartePayOrderDO.setInsOrderNo(tradeRequest.getPlatformTxNo());
        return commonResult;
    }

    String params(HttpServletRequest request) throws Exception {
        // 读取请求体中的内容
        StringBuilder stringBuilder = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line);
            }
        }
        log.warn("国广支付回调返回参数:{}", stringBuilder);
        // 获取完整的 JSON 字符串
        return stringBuilder.toString();
    }

    //转换是如果商户有单独配置 取商户配置
    public GuogPayConfig getPayConfig(String channelConfig, String payOrderNo) {
        // 配置转换
        GuogPayConfig payConfig = JsonUtils.parseObject(channelConfig, GuogPayConfig.class);

        PayOrderDO orderDO = payOrderService.queryPayOrderNoCache(payOrderNo);
        GuogpayAppChannelParams appChannelParams = JsonUtils.parseObject(orderDO.getChannelConfig(), GuogpayAppChannelParams.class);

        if (StringUtils.hasText(appChannelParams.getMerchantNo()))
            payConfig.setMerchantNo(appChannelParams.getMerchantNo());

        if (StringUtils.hasText(appChannelParams.getMd5Key()))
            payConfig.setMd5Key(appChannelParams.getMd5Key());
        return payConfig;
    }
}
